[Chapter Twelve][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly Language: Chapter Twelve
- 12.4 - Passing Procedures as Parameters
12.4 Passing Procedures as Parameters
Many programming languages let you pass a procedure or function name
as a parameter. This lets the caller pass along various actions to perform
inside a procedure. The classic example is a plot
procedure
that graphs some generic math function passed as a parameter to plot
.
Standard Pascal lets you pass procedures and functions by declaring them
as follows:
procedure DoCall(procedure x);
begin
x;
end;
The statement DoCall(xyz);
calls DoCall
that,
in turn, calls procedure xyz
.
Passing a procedure or function as a parameter may seem like an easy task
- just pass the address of the function or procedure as the following example
demonstrates:
procedure PassMe;
begin
Writeln('PassMe was called');
end;
procedure CallPassMe(procedure x);
begin
x;
end;
begin {main}
CallPassMe(PassMe);
end.
The 80x86 code to implement the above could look like the following:
PassMe proc near
print
byte "PassMe was called",cr,lf,0
ret
PassMe endp
CallPassMe proc near
push bp
mov bp, sp
call word ptr [bp+4]
pop bp
ret 2
CallPassMe endp
Main proc near
lea bx, PassMe ;Pass address of PassMe to
push bx ; CallPassMe
call CallPassMe
ExitPgm
Main endp
For an example as simple as the one above, this technique works fine. However,
it does not always work properly if PassMe needs to access non-local variables.
The following Pascal code demonstrates the problem that could occur:
program main;
procedure dummy;
begin end;
procedure Recurse1(i:integer; procedure x);
procedure Print;
begin
writeln(i);
end;
procedure Recurse2(j:integer; procedure y);
begin
if (j=1) then y
else if (j=5) then Recurse1(j-1, Print)
else Recurse1(j-1, y);
end;
begin {Recurse1}
Recurse2(i, x);
end;
begin {Main}
Recurse1(5,dummy);
end.
This code produces the following call sequence:
Recurse1(5,dummy)
--> Recurse2(5,dummy)
--> Recurse1(4,Print)
-->
Recurse2(4,Print)
--> Recurse1(3,Print)
--> Recurse2(3,Print)
-->
Recurse1(2,Print)
--> Recurse2(2,Print)
--> Recurse1(1,Print)
-->
Recurse2(1,Print)
--> Print
Print
will print the value of Recurse1
's i
variable to the standard output. However, there are several activation records
present on the stack that raises the obvious question, "which copy
of i
does Print
display?" Without giving
it much thought, you might conclude that it should print the value "1"
since Recurse2
calls Print
when Recurse1
's
value for i
is one. Note, though, that when Recurse2
passes the address of Print
to Recurse1
, i
's
value is four. Pascal, like most block structured languages, will use the
value of i
at the point Recurse2
passes the address
of Print
to Recurse1
. Hence, the code above should
print the value four, not the value one.
This creates a difficult implementation problem. After all, Print
cannot simply access the display to gain access to the global variable i
- the display's entry for Recurse1
points at the latest copy
of Recurse1
's activation record, not the entry containing the
value four which is what you want.
The most common solution in systems using a display is to make a local copy
of each display whenever calling a procedure or function. When passing a
procedure or function as a parameter, the calling code copies the display
along with the address of the procedure or function. This is why Intel's
enter
instruction makes a copy of the display when building
the activation record.
If you are passing procedures and functions as parameters, you may want
to consider using static links rather than a display. When using a static
link you need only pass a single pointer (the static link) along with the
routine's address. Of course, it is more work to access non-local variables,
but you don't have to copy the display on every call, which is quite expensive.
The following 80x86 code provides the implementation of the above code using
static links:
wp textequ <word ptr>
Dummy proc near
ret
Dummy endp
; PrintIt; (Use the name PrintIt to avoid conflict).
;
; stack:
;
; bp+4: static link.
PrintIt proc near
push bp
mov bp, sp
mov bx, [bp+4] ;Get static link
mov ax, ss:[bx-10] ;Get i's value.
puti
pop bp
ret 2
PrintIt endp
; Recurse1(i:integer; procedure x);
;
; stack:
;
; bp+10: i
; bp+8: x's static link
; bp+6: x's address
Recurse1 proc near
push bp
mov bp, sp
push wp [bp+10] ;Push value of i onto stack.
push wp [bp+8] ;Push x's static link.
push wp [bp+6] ;Push x's address.
push bp ;Push Recurse1's static link.
call Recurse1
pop bp
ret 6
Recurse1 endp
; Recurse2(i:integer; procedure y);
;
; stack:
;
; bp+10: j
; bp+8: y's static link.
; bp+6: y's address.
; bp+4: Recurse2's static link.
Recurse2 proc near
push bp
mov bp, sp
cmp wp [bp+10], 1 ;Is j=1?
jne TryJeq5
push [bp+8] ;y's static link.
call wp [bp+6] ;Call y.
jmp R2Done
TryJeq5: cmp wp [bp+10], 5 ;Is j=5?
jne Call1
mov ax, [bp+10]
dec ax
push ax
push [bp+4] ;Push static link to R1.
lea ax, PrintIt ;Push address of print.
push ax
call Recurse1
jmp R2Done
Call1: mov ax, [bp+10]
dec ax
push ax
push [bp+8] ;Pass along existing
push [bp+6] ; address and link.
call Recurse1
R2Done: pop bp
ret 6
Recurse1 endp
main proc
push bp
mov bp, sp
mov ax, 5 ;Push first parameter.
push ax
push bp ;Dummy static link.
lea ax, Dummy ;Push address of dummy.
push ax
call Recurse1
pop bp
ExitPgm
main endp
There are several ways to improve this code. Of course, this particular
program doesn't really need to maintain a display or static list because
only PrintIt
accesses non-local variables; however, ignore
that fact for the time being and pretend it does. Since you know that PrintIt
only accesses variables at one particular lex level, and the program only
calls PrintIt
indirectly, you can pass a pointer to the appropriate
activation record; this is what the above code does, although it maintains
full static links as well. Compilers must always assume the worst case and
often generate inefficient code. If you study your particular needs, however,
you may be able to improve the efficiency of your code by avoiding much
of the overhead of maintaining static lists or copying displays.
Keep in mind that thunks are special cases of functions that you call indirectly.
They suffer from the same problems and drawbacks as procedure and function
parameters with respect to accessing non-local variables. If such routines
access non-local variables (and thunks almost always will) then you must
exercise care when calling such routines. Fortunately, thunks never cause
indirect recursion (which is responsible for the crazy problems in the Recurse1
/ Recurse2
example) so you can use the display to access any
non-local variables appearing within the thunk.
- 12.4 - Passing Procedures as
Parameters
Art of Assembly Language: Chapter Twelve - 27 SEP 1996
[Chapter Twelve][Previous]
[Next] [Art of
Assembly][Randall Hyde]